#include "cursor_wrapper.h"
#include "wlroots-full.hpp"

#include "log.h"

CursorWrapper::CursorWrapper(Server *server)
    : m_server(server)
{
    /*
     * Creates a cursor, which is a wlroots utility for tracking the cursor
     * image shown on screen.
     */
    m_cursor = wlr_cursor_create();
    wlr_cursor_attach_output_layout(m_cursor, m_server->m_output_layout);

    /* Creates an xcursor manager, another wlroots utility which loads up
     * Xcursor themes to source cursor images from and makes sure that cursor
     * images are available at all scale factors on the screen (necessary for
     * HiDPI support). */
    m_cursor_mgr = wlr_xcursor_manager_create(NULL, 24);

    /*
     * wlr_cursor *only* displays an image on screen. It does not move around
     * when the pointer moves. However, we can attach input devices to it, and
     * it will generate aggregate events for all of them. In these events, we
     * can choose how we want to process them, forwarding them to clients and
     * moving the cursor around. More detail on this process is described in
     * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html.
     *
     * And more comments are sprinkled throughout the notify functions above.
     */
    m_cursor_mode = BWL_CURSOR_PASSTHROUGH;

    m_cursor_motion.connect(&m_cursor->events.motion, [=](void* data) {
        wlr_pointer_motion_event* event = (wlr_pointer_motion_event*)data;
        cursor_motion(event);
    });

    m_cursor_motion_absolute.connect(&m_cursor->events.motion_absolute, [=](void* data) {
        wlr_pointer_motion_absolute_event* event = (wlr_pointer_motion_absolute_event*)data;
        cursor_motion_absolute(event);
    });

    m_cursor_button.connect(&m_cursor->events.button, [=](void *data) {
        wlr_pointer_button_event* event = (wlr_pointer_button_event*)data;
        cursor_button(event);
    });

    m_cursor_axis.connect(&m_cursor->events.axis, [=](void* data) {
        wlr_pointer_axis_event* event = (wlr_pointer_axis_event*)data;
        cursor_axis(event);
    });

    m_cursor_frame.connect(&m_cursor->events.frame, [=](void* data) {
        cursor_frame();
    });
}

CursorWrapper::~CursorWrapper()
{
}

void CursorWrapper::set_cursor_default()
{
    wlr_cursor_set_xcursor(m_cursor, m_cursor_mgr, "default");
}

void CursorWrapper::cursor_motion(wlr_pointer_motion_event *event)
{
    wlr_cursor_move(m_cursor, &event->pointer->base,
                    event->delta_x, event->delta_y);
    process_cursor_motion(event->time_msec);
}

void CursorWrapper::cursor_motion_absolute(wlr_pointer_motion_absolute_event *event)
{
    wlr_cursor_warp_absolute(m_cursor, &event->pointer->base, event->x,
                             event->y);
    process_cursor_motion(event->time_msec);
}

void CursorWrapper::cursor_button(wlr_pointer_button_event *event)
{
    sig_cursor_button.emit(m_cursor->x, m_cursor->y, event);

#if 0
    /* Notify the client with pointer focus that a button press has occurred */
    wlr_seat_pointer_notify_button(server->seat,
                                   event->time_msec, event->button, event->state);
    double sx, sy;
    struct wlr_surface *surface = NULL;
    struct tinywl_toplevel *toplevel = desktop_toplevel_at(server,
                                                           server->cursor->x, server->cursor->y, &surface, &sx, &sy);
    if (event->state == WLR_BUTTON_RELEASED) {
        /* If you released any buttons, we exit interactive move/resize mode. */
        reset_cursor_mode(server);
    } else {
        /* Focus that client if the button was _pressed_ */
        focus_toplevel(toplevel, surface);
    }
#endif
}

void CursorWrapper::cursor_axis(wlr_pointer_axis_event *event)
{
    sig_cursor_axis.emit(event);
}

void CursorWrapper::cursor_frame()
{
    sig_cursor_frame.emit();
}

void CursorWrapper::process_cursor_motion(uint32_t time)
{
    wlr_log(WLR_DEBUG, "cursor motion, x:%lf, y:%lf, time:%u", m_cursor->x, m_cursor->y, time);
    /* If the mode is non-passthrough, delegate to those functions. */
    if (m_cursor_mode == BWL_CURSOR_MOVE) {
        process_cursor_move(time);
        return;
    } else if (m_cursor_mode == BWL_CURSOR_RESIZE) {
        process_cursor_resize(time);
        return;
    }

    sig_cursor_motion.emit(m_cursor->x, m_cursor->y, time);

#if 0
    /* Otherwise, find the toplevel under the pointer and send the event along. */
    double sx, sy;
    struct wlr_seat *seat = server->seat;
    struct wlr_surface *surface = NULL;
    struct tinywl_toplevel *toplevel = desktop_toplevel_at(server,
                                                           server->cursor->x, server->cursor->y, &surface, &sx, &sy);
    if (!toplevel) {
        /* If there's no toplevel under the cursor, set the cursor image to a
         * default. This is what makes the cursor image appear when you move it
         * around the screen, not over any toplevels. */
        wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default");
    }
    if (surface) {
        /*
         * Send pointer enter and motion events.
         *
         * The enter event gives the surface "pointer focus", which is distinct
         * from keyboard focus. You get pointer focus by moving the pointer over
         * a window.
         *
         * Note that wlroots will avoid sending duplicate enter/motion events if
         * the surface has already has pointer focus or if the client is already
         * aware of the coordinates passed.
         */
        wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
        wlr_seat_pointer_notify_motion(seat, time, sx, sy);
    } else {
        /* Clear pointer focus so future button events and such are not sent to
         * the last client to have the cursor over it. */
        wlr_seat_pointer_clear_focus(seat);
    }
#endif
}

void CursorWrapper::process_cursor_move(uint32_t time)
{
    sig_cursor_move.emit(m_cursor->x - m_grab_x, m_cursor->y - m_grab_y);
}

void CursorWrapper::process_cursor_resize(uint32_t time)
{
    /*
     * Resizing the grabbed toplevel can be a little bit complicated, because we
     * could be resizing from any corner or edge. This not only resizes the
     * toplevel on one or two axes, but can also move the toplevel if you resize
     * from the top or left edges (or top-left corner).
     *
     * Note that some shortcuts are taken here. In a more fleshed-out
     * compositor, you'd wait for the client to prepare a buffer at the new
     * size, then commit any movement that was prepared.
     */
    // struct tinywl_toplevel *toplevel = server->grabbed_toplevel;
    double border_x = m_cursor->x - m_grab_x;
    double border_y = m_cursor->y - m_grab_y;
    int new_left = m_grab_geobox.x;
    int new_right = m_grab_geobox.x + m_grab_geobox.width;
    int new_top = m_grab_geobox.y;
    int new_bottom = m_grab_geobox.y + m_grab_geobox.height;

    if (m_resize_edges & WLR_EDGE_TOP) {
        new_top = border_y;
        if (new_top >= new_bottom) {
            new_top = new_bottom - 1;
        }
    } else if (m_resize_edges & WLR_EDGE_BOTTOM) {
        new_bottom = border_y;
        if (new_bottom <= new_top) {
            new_bottom = new_top + 1;
        }
    }
    if (m_resize_edges & WLR_EDGE_LEFT) {
        new_left = border_x;
        if (new_left >= new_right) {
            new_left = new_right - 1;
        }
    } else if (m_resize_edges & WLR_EDGE_RIGHT) {
        new_right = border_x;
        if (new_right <= new_left) {
            new_right = new_left + 1;
        }
    }
    sig_cursor_resize.emit(new_left, new_right, new_top, new_bottom, time);
}
